// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>

#include <drm/drmP.h>
#include <drm/drm_panel.h>

#include "../lombo/lombo_vo_manage.h"

#define ACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 0 : 1)
#define INACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 1 : 0)

#define WKS43178_HBP		(20)
#define WKS43178_HFP		(40)
#define WKS43178_HSW		(10)
#define WKS43178_HACT		(800)
#define WKS43178_VBP		(10)
#define WKS43178_VFP		(15)
#define WKS43178_VSW		(10)
#define WKS43178_VACT		(480)
#define WKS43178_DCLK		(27000000)

#define CMD_ALIGN		(0)
#define DCS_MDELAY_FLAG		(0)

struct panel_wks43178_desc {
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	const struct display_timing *timings;
	unsigned int num_timings;

	unsigned int bpc;

	struct {
		unsigned int width;
		unsigned int height;
	} size;

	/**
	 * @prepare: the time (in milliseconds) that it takes for the panel to
	 *           become ready and start receiving video data
	 * @enable: the time (in milliseconds) that it takes for the panel to
	 *          display the first valid frame after starting to receive
	 *          video data
	 * @disable: the time (in milliseconds) that it takes for the panel to
	 *           turn the display off (no content is visible)
	 * @unprepare: the time (in milliseconds) that it takes for the panel
	 *             to power itself down completely
	 */
	struct {
		unsigned int prepare;
		unsigned int enable;
		unsigned int disable;
		unsigned int unprepare;
	} delay;

	lombo_vo_dev_if_t interface;
	u32 cmd_align;
	unsigned long flag;
	enum lombo_vo_pixel_format format;
};

struct panel_wks43178 {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	const struct panel_wks43178_desc *desc;

	struct backlight_device *backlight;
	struct regulator *supply;

	int power_gpio;
	enum of_gpio_flags power_gpio_flag;
	int reset_gpio;
	enum of_gpio_flags reset_gpio_flag;
};

u32 init_code[] = {
	/********* Start Initial Sequence ******/
	/**********LV2 Page 1 enable************/
	2, 0xF000, 0x0055,
	2, 0xF001, 0x00AA,
	2, 0xF002, 0x0052,
	2, 0xF003, 0x0008,
	2, 0xF004, 0x0001,

	/************AVDD Set AVDD 5.2V*********/
	2, 0xB000, 0x000D,
	2, 0xB001, 0x000D,
	2, 0xB002, 0x000D,

	/***********AVDD ratio******************/
	2, 0xB600, 0x0034,
	2, 0xB601, 0x0034,
	2, 0xB602, 0x0034,

	/***********AVEE  -5.2V*****************/
	2, 0xB100, 0x000D,
	2, 0xB101, 0x000D,
	2, 0xB102, 0x000D,

	/**************AVEE ratio***************/
	2, 0xB700, 0x0034,
	2, 0xB701, 0x0034,
	2, 0xB702, 0x0034,

	/*************VCL  -2.5V*****************/
	2, 0xB200, 0x0000,
	2, 0xB201, 0x0000,
	2, 0xB202, 0x0000,

	/*************VCL ratio******************/
	2, 0xB800, 0x0024,
	2, 0xB801, 0x0024,
	2, 0xB802, 0x0024,

	/*************VGH 15V  (Free pump)********/
	2, 0xBF00, 0x0001,
	2, 0xB300, 0x000F,
	2, 0xB301, 0x000F,
	2, 0xB302, 0x000F,

	/**************VGH ratio******************/
	2, 0xB900, 0x0034,
	2, 0xB901, 0x0034,
	2, 0xB902, 0x0034,

	/**************VGL_REG -10V***************/
	2, 0xB500, 0x0008,
	2, 0xB501, 0x0008,
	2, 0xB502, 0x0008,

	2, 0xC200, 0x0003,

	/****************VGLX ratio***************/
	2, 0xBA00, 0x0024,
	2, 0xBA01, 0x0024,
	2, 0xBA02, 0x0024,

	/************VGMP/VGSP 4.5V/0V************/
	2, 0xBC00, 0x0000,
	2, 0xBC01, 0x0078,
	2, 0xBC02, 0x0000,

	/***********VGMN/VGSN -4.5V/0V************/
	2, 0xBD00, 0x0000,
	2, 0xBD01, 0x0078,
	2, 0xBD02, 0x0000,

	/***********VCOM  -1.25V******************/
	2, 0xBE00, 0x0000,
	2, 0xBE01, 0x0067,

	/***********Gamma Setting*****************/
	2, 0xD100, 0x0000,
	2, 0xD101, 0x0032,
	2, 0xD102, 0x0000,
	2, 0xD103, 0x0033,
	2, 0xD104, 0x0000,
	2, 0xD105, 0x0041,
	2, 0xD106, 0x0000,
	2, 0xD107, 0x005A,
	2, 0xD108, 0x0000,
	2, 0xD109, 0x0076,
	2, 0xD10A, 0x0000,
	2, 0xD10B, 0x00A7,
	2, 0xD10C, 0x0000,
	2, 0xD10D, 0x00CF,
	2, 0xD10E, 0x0001,
	2, 0xD10F, 0x0009,
	2, 0xD110, 0x0001,
	2, 0xD111, 0x0036,
	2, 0xD112, 0x0001,
	2, 0xD113, 0x0073,
	2, 0xD114, 0x0001,
	2, 0xD115, 0x009F,
	2, 0xD116, 0x0001,
	2, 0xD117, 0x00DF,
	2, 0xD118, 0x0002,
	2, 0xD119, 0x0010,
	2, 0xD11A, 0x0002,
	2, 0xD11B, 0x0011,
	2, 0xD11C, 0x0002,
	2, 0xD11D, 0x003D,
	2, 0xD11E, 0x0002,
	2, 0xD11F, 0x0069,
	2, 0xD120, 0x0002,
	2, 0xD121, 0x0081,
	2, 0xD122, 0x0002,
	2, 0xD123, 0x009D,
	2, 0xD124, 0x0002,
	2, 0xD125, 0x00AD,
	2, 0xD126, 0x0002,
	2, 0xD127, 0x00C3,
	2, 0xD128, 0x0002,
	2, 0xD129, 0x00D0,
	2, 0xD12A, 0x0002,
	2, 0xD12B, 0x00E2,
	2, 0xD12C, 0x0002,
	2, 0xD12D, 0x00EE,
	2, 0xD12E, 0x0003,
	2, 0xD12F, 0x0001,
	2, 0xD130, 0x0003,
	2, 0xD131, 0x0026,
	2, 0xD132, 0x0003,
	2, 0xD133, 0x008E,

	2, 0xD200, 0x0000,
	2, 0xD201, 0x0032,
	2, 0xD202, 0x0000,
	2, 0xD203, 0x0033,
	2, 0xD204, 0x0000,
	2, 0xD205, 0x0041,
	2, 0xD206, 0x0000,
	2, 0xD207, 0x005A,
	2, 0xD208, 0x0000,
	2, 0xD209, 0x0076,
	2, 0xD20A, 0x0000,
	2, 0xD20B, 0x00A7,
	2, 0xD20C, 0x0000,
	2, 0xD20D, 0x00CF,
	2, 0xD20E, 0x0001,
	2, 0xD20F, 0x0009,
	2, 0xD210, 0x0001,
	2, 0xD211, 0x0036,
	2, 0xD212, 0x0001,
	2, 0xD213, 0x0073,
	2, 0xD214, 0x0001,
	2, 0xD215, 0x009F,
	2, 0xD216, 0x0001,
	2, 0xD217, 0x00DF,
	2, 0xD218, 0x0002,
	2, 0xD219, 0x0010,
	2, 0xD21A, 0x0002,
	2, 0xD21B, 0x0011,
	2, 0xD21C, 0x0002,
	2, 0xD21D, 0x003D,
	2, 0xD21E, 0x0002,
	2, 0xD21F, 0x0069,
	2, 0xD220, 0x0002,
	2, 0xD221, 0x0081,
	2, 0xD222, 0x0002,
	2, 0xD223, 0x009D,
	2, 0xD224, 0x0002,
	2, 0xD225, 0x00AD,
	2, 0xD226, 0x0002,
	2, 0xD227, 0x00C3,
	2, 0xD228, 0x0002,
	2, 0xD229, 0x00D0,
	2, 0xD22A, 0x0002,
	2, 0xD22B, 0x00E2,
	2, 0xD22C, 0x0002,
	2, 0xD22D, 0x00EE,
	2, 0xD22E, 0x0003,
	2, 0xD22F, 0x0001,
	2, 0xD230, 0x0003,
	2, 0xD231, 0x0026,
	2, 0xD232, 0x0003,
	2, 0xD233, 0x008E,

	2, 0xD300, 0x0000,
	2, 0xD301, 0x0032,
	2, 0xD302, 0x0000,
	2, 0xD303, 0x0033,
	2, 0xD304, 0x0000,
	2, 0xD305, 0x0041,
	2, 0xD306, 0x0000,
	2, 0xD307, 0x005A,
	2, 0xD308, 0x0000,
	2, 0xD309, 0x0076,
	2, 0xD30A, 0x0000,
	2, 0xD30B, 0x00A7,
	2, 0xD30C, 0x0000,
	2, 0xD30D, 0x00CF,
	2, 0xD30E, 0x0001,
	2, 0xD30F, 0x0009,
	2, 0xD310, 0x0001,
	2, 0xD311, 0x0036,
	2, 0xD312, 0x0001,
	2, 0xD313, 0x0073,
	2, 0xD314, 0x0001,
	2, 0xD315, 0x009F,
	2, 0xD316, 0x0001,
	2, 0xD317, 0x00DF,
	2, 0xD318, 0x0002,
	2, 0xD319, 0x0010,
	2, 0xD31A, 0x0002,
	2, 0xD31B, 0x0011,
	2, 0xD31C, 0x0002,
	2, 0xD31D, 0x003D,
	2, 0xD31E, 0x0002,
	2, 0xD31F, 0x0069,
	2, 0xD320, 0x0002,
	2, 0xD321, 0x0081,
	2, 0xD322, 0x0002,
	2, 0xD323, 0x009D,
	2, 0xD324, 0x0002,
	2, 0xD325, 0x00AD,
	2, 0xD326, 0x0002,
	2, 0xD327, 0x00C3,
	2, 0xD328, 0x0002,
	2, 0xD329, 0x00D0,
	2, 0xD32A, 0x0002,
	2, 0xD32B, 0x00E2,
	2, 0xD32C, 0x0002,
	2, 0xD32D, 0x00EE,
	2, 0xD32E, 0x0003,
	2, 0xD32F, 0x0001,
	2, 0xD330, 0x0003,
	2, 0xD331, 0x0026,
	2, 0xD332, 0x0003,
	2, 0xD333, 0x008E,

	2, 0xD400, 0x0000,
	2, 0xD401, 0x0032,
	2, 0xD402, 0x0000,
	2, 0xD403, 0x0033,
	2, 0xD404, 0x0000,
	2, 0xD405, 0x0041,
	2, 0xD406, 0x0000,
	2, 0xD407, 0x005A,
	2, 0xD408, 0x0000,
	2, 0xD409, 0x0076,
	2, 0xD40A, 0x0000,
	2, 0xD40B, 0x00A7,
	2, 0xD40C, 0x0000,
	2, 0xD40D, 0x00CF,
	2, 0xD40E, 0x0001,
	2, 0xD40F, 0x0009,
	2, 0xD410, 0x0001,
	2, 0xD411, 0x0036,
	2, 0xD412, 0x0001,
	2, 0xD413, 0x0073,
	2, 0xD414, 0x0001,
	2, 0xD415, 0x009F,
	2, 0xD416, 0x0001,
	2, 0xD417, 0x00DF,
	2, 0xD418, 0x0002,
	2, 0xD419, 0x0010,
	2, 0xD41A, 0x0002,
	2, 0xD41B, 0x0011,
	2, 0xD41C, 0x0002,
	2, 0xD41D, 0x003D,
	2, 0xD41E, 0x0002,
	2, 0xD41F, 0x0069,
	2, 0xD420, 0x0002,
	2, 0xD421, 0x0081,
	2, 0xD422, 0x0002,
	2, 0xD423, 0x009D,
	2, 0xD424, 0x0002,
	2, 0xD425, 0x00AD,
	2, 0xD426, 0x0002,
	2, 0xD427, 0x00C3,
	2, 0xD428, 0x0002,
	2, 0xD429, 0x00D0,
	2, 0xD42A, 0x0002,
	2, 0xD42B, 0x00E2,
	2, 0xD42C, 0x0002,
	2, 0xD42D, 0x00EE,
	2, 0xD42E, 0x0003,
	2, 0xD42F, 0x0001,
	2, 0xD430, 0x0003,
	2, 0xD431, 0x0026,
	2, 0xD432, 0x0003,
	2, 0xD433, 0x008E,

	2, 0xD500, 0x0000,
	2, 0xD501, 0x0032,
	2, 0xD502, 0x0000,
	2, 0xD503, 0x0033,
	2, 0xD504, 0x0000,
	2, 0xD505, 0x0041,
	2, 0xD506, 0x0000,
	2, 0xD507, 0x005A,
	2, 0xD508, 0x0000,
	2, 0xD509, 0x0076,
	2, 0xD50A, 0x0000,
	2, 0xD50B, 0x00A7,
	2, 0xD50C, 0x0000,
	2, 0xD50D, 0x00CF,
	2, 0xD50E, 0x0001,
	2, 0xD50F, 0x0009,
	2, 0xD510, 0x0001,
	2, 0xD511, 0x0036,
	2, 0xD512, 0x0001,
	2, 0xD513, 0x0073,
	2, 0xD514, 0x0001,
	2, 0xD515, 0x009F,
	2, 0xD516, 0x0001,
	2, 0xD517, 0x00DF,
	2, 0xD518, 0x0002,
	2, 0xD519, 0x0010,
	2, 0xD51A, 0x0002,
	2, 0xD51B, 0x0011,
	2, 0xD51C, 0x0002,
	2, 0xD51D, 0x003D,
	2, 0xD51E, 0x0002,
	2, 0xD51F, 0x0069,
	2, 0xD520, 0x0002,
	2, 0xD521, 0x0081,
	2, 0xD522, 0x0002,
	2, 0xD523, 0x009D,
	2, 0xD524, 0x0002,
	2, 0xD525, 0x00AD,
	2, 0xD526, 0x0002,
	2, 0xD527, 0x00C3,
	2, 0xD528, 0x0002,
	2, 0xD529, 0x00D0,
	2, 0xD52A, 0x0002,
	2, 0xD52B, 0x00E2,
	2, 0xD52C, 0x0002,
	2, 0xD52D, 0x00EE,
	2, 0xD52E, 0x0003,
	2, 0xD52F, 0x0001,
	2, 0xD530, 0x0003,
	2, 0xD531, 0x0026,
	2, 0xD532, 0x0003,
	2, 0xD533, 0x008E,

	2, 0xD600, 0x0000,
	2, 0xD601, 0x0032,
	2, 0xD602, 0x0000,
	2, 0xD603, 0x0033,
	2, 0xD604, 0x0000,
	2, 0xD605, 0x0041,
	2, 0xD606, 0x0000,
	2, 0xD607, 0x005A,
	2, 0xD608, 0x0000,
	2, 0xD609, 0x0076,
	2, 0xD60A, 0x0000,
	2, 0xD60B, 0x00A7,
	2, 0xD60C, 0x0000,
	2, 0xD60D, 0x00CF,
	2, 0xD60E, 0x0001,
	2, 0xD60F, 0x0009,
	2, 0xD610, 0x0001,
	2, 0xD611, 0x0036,
	2, 0xD612, 0x0001,
	2, 0xD613, 0x0073,
	2, 0xD614, 0x0001,
	2, 0xD615, 0x009F,
	2, 0xD616, 0x0001,
	2, 0xD617, 0x00DF,
	2, 0xD618, 0x0002,
	2, 0xD619, 0x0010,
	2, 0xD61A, 0x0002,
	2, 0xD61B, 0x0011,
	2, 0xD61C, 0x0002,
	2, 0xD61D, 0x003D,
	2, 0xD61E, 0x0002,
	2, 0xD61F, 0x0069,
	2, 0xD620, 0x0002,
	2, 0xD621, 0x0081,
	2, 0xD622, 0x0002,
	2, 0xD623, 0x009D,
	2, 0xD624, 0x0002,
	2, 0xD625, 0x00AD,
	2, 0xD626, 0x0002,
	2, 0xD627, 0x00C3,
	2, 0xD628, 0x0002,
	2, 0xD629, 0x00D0,
	2, 0xD62A, 0x0002,
	2, 0xD62B, 0x00E2,
	2, 0xD62C, 0x0002,
	2, 0xD62D, 0x00EE,
	2, 0xD62E, 0x0003,
	2, 0xD62F, 0x0001,
	2, 0xD630, 0x0003,
	2, 0xD631, 0x0026,
	2, 0xD632, 0x0003,
	2, 0xD633, 0x008E,

	/**************LV2 Page 0 enable********/
	2, 0xF000, 0x0055,
	2, 0xF001, 0x00AA,
	2, 0xF002, 0x0052,
	2, 0xF003, 0x0008,
	2, 0xF004, 0x0000,

	/************480x800********************/
	2, 0xB500, 0x0050,

	/************Display control************/
	2, 0xB100, 0x00CC,
	2, 0xB101, 0x0000,

	/************Source hold time***********/
	2, 0xB600, 0x0005,

	/***********Gate EQ control*************/
	2, 0xB700, 0x0070,
	2, 0xB701, 0x0070,

	/***********Source EQ control (Mode 2)**/
	2, 0xB800, 0x0001,
	2, 0xB801, 0x0003,
	2, 0xB802, 0x0003,
	2, 0xB803, 0x0003,

	/**********Inversion mode  (2-dot)******/
	2, 0xBC00, 0x0002,
	2, 0xBC01, 0x0000,
	2, 0xBC02, 0x0000,

	/************Frame rate*****************/
	2, 0xBD00, 0x0001,
	2, 0xBD01, 0x0084,
	2, 0xBD02, 0x001C,
	2, 0xBD03, 0x001C,
	2, 0xBD04, 0x0000,

	/*****Timng control 4H w/ 4-Delayms ****/
	2, 0xC900, 0x00D0,
	2, 0xC901, 0x0002,
	2, 0xC902, 0x0050,
	2, 0xC903, 0x0050,
	2, 0xC904, 0x0050,

	2, 0x3600, 0x0000,
	2, 0x3500, 0x0000,

	2, 0x3A00, 0x0005,

	2, 0X3600, 0X60,
	2, 0X2A02, 0X03,
	2, 0X2A03, 0X1F,
	2, 0X2B02, 0X01,
	2, 0X2B03, 0XDF,

	1, 0x1100,
	DCS_MDELAY_FLAG, 120, /* Delay 120ms */
	1, 0x2900,
	DCS_MDELAY_FLAG, 50, /* Delay 50ms */
	1, 0x2C00,
};

static inline struct panel_wks43178 *to_panel_wks43178(struct drm_panel *panel)
{
	return container_of(panel, struct panel_wks43178, base);
}

static int panel_wks43178_disable(struct drm_panel *panel)
{
	struct panel_wks43178 *p = to_panel_wks43178(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		p->backlight->props.state |= BL_CORE_FBBLANK;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_wks43178_unprepare(struct drm_panel *panel)
{
	struct panel_wks43178 *p = to_panel_wks43178(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	if (p->reset_gpio)
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio,
			INACTIVE(p->power_gpio_flag));

	if (p->supply)
		regulator_disable(p->supply);

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static ssize_t panel_wks43178_wr_init_code(struct drm_panel *panel,
		u32 *ini_code, u32 len)
{
	u32 i = 0, bytes = 0;
	struct panel_wks43178 *p = to_panel_wks43178(panel);
	struct lombo_vo_device *vo = to_lombo_vo_device(p->dev);

	if (!ini_code)
		return 0;

	while (i < len) {
		if (ini_code[i] == 0) {
			mdelay(ini_code[i + 1]);
			i += 2;
		} else {
			lombo_vo_write_buffer(vo, ini_code + i + 1,
						ini_code[i]);
			i += ini_code[i] + 1;
			bytes += ini_code[i];
		}
	}

	return bytes;
}

static int panel_wks43178_prepare(struct drm_panel *panel)
{
	struct panel_wks43178 *p = to_panel_wks43178(panel);
	int err;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			DRM_ERROR("failed to enable supply\n");
			return err;
		}
	}

	if (p->power_gpio) {
		gpio_direction_output(p->power_gpio,
			ACTIVE(p->power_gpio_flag));
		msleep(100);
	}

	if (p->reset_gpio) {
		gpio_direction_output(p->reset_gpio,
			ACTIVE(p->reset_gpio_flag));
		msleep(100);
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));
		msleep(100);
		gpio_direction_output(p->reset_gpio,
			ACTIVE(p->reset_gpio_flag));
		msleep(100);
	}

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	panel_wks43178_wr_init_code(panel, &init_code[0],
		sizeof(init_code) / sizeof((init_code)[0]));

	return 0;
}

static int panel_wks43178_enable(struct drm_panel *panel)
{
	struct panel_wks43178 *p = to_panel_wks43178(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.state &= ~BL_CORE_FBBLANK;
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_wks43178_get_fixed_modes(
	struct panel_wks43178 *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);
		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;

	return num;
}

static int panel_wks43178_get_modes(struct drm_panel *panel)
{
	struct panel_wks43178 *p = to_panel_wks43178(panel);
	int num = 0;

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	/* add hard-coded panel modes */
	num += panel_wks43178_get_fixed_modes(p);

	return num;
}

static int panel_wks43178_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static const struct drm_panel_funcs panel_wks43178_funcs = {
	.disable = panel_wks43178_disable,
	.unprepare = panel_wks43178_unprepare,
	.prepare = panel_wks43178_prepare,
	.enable = panel_wks43178_enable,
	.get_modes = panel_wks43178_get_modes,
	.get_timings = panel_wks43178_get_timings,
};

static int panel_wks43178_probe(struct device *dev,
			       const struct panel_wks43178_desc *desc)
{
	struct device_node *np = dev->of_node;
	struct panel_wks43178 *panel;
	enum of_gpio_flags flags;
	int err;
	struct lombo_vo_device *vo = to_lombo_vo_device(dev);

	dev_dbg(dev, "%s,%d\n", __FILE__, __LINE__);

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -ENOMEM;

	panel->power_gpio = of_get_named_gpio_flags(
		np, "power_gpio", 0, &flags);
	panel->power_gpio_flag = flags;
	DRM_DEBUG_KMS("power_gpio %d, flags 0x%x\n", panel->power_gpio, flags);
	if (!gpio_is_valid(panel->power_gpio)) {
		DRM_ERROR("power_gpio %d invalid\n", panel->power_gpio);
		panel->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->power_gpio);
			panel->power_gpio = 0;
		}
	}

	panel->reset_gpio = of_get_named_gpio_flags(
		np, "reset_gpio", 0, &flags);
	panel->reset_gpio_flag = flags;
	DRM_DEBUG_KMS("reset_gpio %d, flags 0x%x\n", panel->reset_gpio, flags);
	if (!gpio_is_valid(panel->reset_gpio)) {
		DRM_ERROR("reset_gpio %d invalid\n", panel->reset_gpio);
		panel->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->reset_gpio);
			panel->reset_gpio = 0;
		}
	}

	vo->interface = desc->interface;
	vo->cmd_align = desc->cmd_align;
	vo->format = desc->format;
	vo->mode_flags = desc->flag;

	panel->enabled = false;
	panel->prepared = false;
	panel->desc = desc;
	panel->dev = dev;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_wks43178_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;

	lombo_vo_attach(vo);
	if (err < 0)
		goto err_out;

	dev_set_drvdata(dev, panel);

	DRM_DEBUG_KMS("[%d] over.\n", __LINE__);

	return 0;

err_out:
	return err;
}

static int panel_wks43178_remove(struct lombo_vo_device *vo)
{
	struct panel_wks43178 *panel = dev_get_drvdata(&vo->dev);

	lombo_vo_detach(vo);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_wks43178_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_wks43178_shutdown(struct lombo_vo_device *vo)
{
	struct panel_wks43178 *panel = dev_get_drvdata(&vo->dev);

	panel_wks43178_disable(&panel->base);
}

static struct drm_display_mode wks43178_mode = {
	/* dclk_freq */
	.clock = WKS43178_DCLK / 1000,
	/* width */
	.hdisplay = WKS43178_HACT,
	/* hsync_start = hdisplay + hfp */
	.hsync_start = WKS43178_HACT + WKS43178_HFP,
	/* hsync_end = hdisplay + hfp + hsw */
	.hsync_end = WKS43178_HACT + WKS43178_HFP + WKS43178_HSW,
	/* htotal = hdisplay + hfp + hsw + hbp */
	.htotal = WKS43178_HACT + WKS43178_HFP + WKS43178_HSW + WKS43178_HBP,
	/* height */
	.vdisplay = WKS43178_VACT,
	/* vsync_start = vdisplay + vfp */
	.vsync_start = WKS43178_VACT + WKS43178_VFP,
	/* vsync_end = vdisplay + vfp + vsw */
	.vsync_end = WKS43178_VACT + WKS43178_VFP + WKS43178_VSW,
	/* vtotal = vdisplay + vfp + vsw + vbp */
	.vtotal = WKS43178_VACT + WKS43178_VFP + WKS43178_VSW + WKS43178_VBP,
	.vrefresh = 60,
};

static const struct panel_wks43178_desc wks43178_desc = {
	.modes = &wks43178_mode,
	.num_modes = 1,
	.bpc = 6,
	.size = {
		.width = 69,
		.height = 123,
	},
	.delay = {
		.prepare = 50,
		.unprepare = 50,
		.enable = 50,
		.disable = 50,
	},
	.interface = LOMBO_VO_DEV_CPU_16BIT_RGB565,
	.cmd_align = CMD_ALIGN,
	.flag = LOMBO_VO_MODE_POS_TE,
	.format = LOMBO_VO_FMT_RGB565
};

static const struct of_device_id panel_wks43178_of_match[] = {
	{
		.compatible = "panel-wks43178",
		.data = &wks43178_desc,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_wks43178_of_match);

static int panel_wks43178_pre_probe(struct lombo_vo_device *vo)
{
	const struct of_device_id *id;

	if (!of_device_is_available(vo->dev.of_node))
		return -ENODEV;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	id = of_match_node(panel_wks43178_of_match, vo->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_wks43178_probe(&vo->dev, id->data);
}

static struct lombo_vo_driver panel_wks43178_driver = {
	.driver = {
		.name = "panel-wks43178",
		.of_match_table = panel_wks43178_of_match,
	},
	.probe = panel_wks43178_pre_probe,
	.remove = panel_wks43178_remove,
	.shutdown = panel_wks43178_shutdown,
};

static int __init panel_wks43178_init(void)
{
	int err;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	err = lombo_vo_driver_register(&panel_wks43178_driver);
	if (err < 0)
		return err;

	return 0;
}
module_init(panel_wks43178_init);

static void __exit panel_wks43178_exit(void)
{
	lombo_vo_driver_unregister(&panel_wks43178_driver);
}
module_exit(panel_wks43178_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("LOMBO VO Driver for panel-wks43178");
MODULE_LICENSE("GPL and additional rights");
